/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.viewfinder.view.internal.futures

import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Preconditions
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

/**
 * A [ListenableFuture] that supports chains of operations. For example:
 * <pre>`ListenableFuture<Boolean> adminIsLoggedIn =
 * FutureChain.from(usersDatabase.getAdminUser())
 * .transform(User::getId, directExecutor())
 * .transform(ActivityService::isLoggedIn, threadPool);
 * `</pre> *
 *
 * @param <V> </V>
 */
open class FutureChain<V> : ListenableFuture<V> {
    private val mDelegate: ListenableFuture<V>
    private var mCompleter: CallbackToFutureAdapter.Completer<V>? = null

    internal constructor(delegate: ListenableFuture<V>) {
        mDelegate = Preconditions.checkNotNull(delegate)
    }

    internal constructor() {
        mDelegate =
            CallbackToFutureAdapter.getFuture { completer ->
                Preconditions.checkState(mCompleter == null, "The result can only set once!")
                mCompleter = completer
                "FutureChain[" + this@FutureChain + "]"
            }
    }

    override fun addListener(listener: Runnable, executor: Executor) {
        mDelegate.addListener(listener, executor)
    }

    override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
        return mDelegate.cancel(mayInterruptIfRunning)
    }

    override fun isCancelled(): Boolean {
        return mDelegate.isCancelled
    }

    override fun isDone(): Boolean {
        return mDelegate.isDone
    }

    @Throws(InterruptedException::class, ExecutionException::class)
    override fun get(): V? {
        return mDelegate.get()
    }

    @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
    override fun get(timeout: Long, unit: TimeUnit): V? {
        return mDelegate[timeout, unit]
    }

    fun set(value: V?): Boolean {
        return if (mCompleter != null) {
            mCompleter!!.set(value)
        } else false
    }

    fun setException(throwable: Throwable): Boolean {
        return if (mCompleter != null) {
            mCompleter!!.setException(throwable)
        } else false
    }

    companion object {
        /**
         * Converts the given `ListenableFuture` to an equivalent `FutureChain`.
         *
         * If the given `ListenableFuture` is already a `FutureChain`, it is returned directly. If
         * not, it is wrapped in a `FutureChain` that delegates all calls to the original
         * `ListenableFuture`.
         *
         * @return directly if input a FutureChain or a ListenableFuture wrapped by FutureChain.
         */
        fun <V> from(future: ListenableFuture<V>): FutureChain<V> {
            return if (future is FutureChain<*>) future as FutureChain<V> else FutureChain(future)
        }
    }
}
